//
// Created by liaoy on 2021/8/19.
//

#include "em-vm.h"

#define MERGE_BYTE2(b1, b2) ((b1) | ((b2) << 8))
#define MERGE_BYTE4(b1, b2, b3, b4) ((b1) | ((b2) << 8) | ((b3) << 16) | ((b4) << 24))
#define MATH_OP(__op)                                       \
    {                                                       \
        uint32_t reg = next_byte(vm);                       \
        uint32_t src1 = next_byte(vm);                      \
        uint32_t src2 = next_byte(vm);                      \
        if (vm->registers[reg].type == STRING               \
            && vm->registers[reg].content.string != NULL)   \
        {                                                   \
            vm_free(vm->registers[reg].content.string);     \
        }                                                   \
        uint32_t val1 = vm->registers[src1].content.integer;\
        uint32_t val2 = vm->registers[src2].content.integer;\
        vm->registers[reg].content.integer = val1 __op val2;\
        vm->registers[reg].type = INTEGER;                  \
        if (vm->registers[reg].content.integer == 0)        \
        {                                                   \
            vm->z_flag = true;                              \
        }                                                   \
        else                                                \
        {                                                   \
            vm->z_flag = false;                             \
        }                                                   \
        vm->ip += 1;                                        \
        break;                                              \
    }

#define MATH_M_OP(__op)                                       \
    {                                                         \
        uint32_t b1, b2, b3, b4;                              \
        b1 = next_byte(vm);                                   \
        b2 = next_byte(vm);                                   \
        b3 = next_byte(vm);                                   \
        b4 = next_byte(vm);                                   \
        uint32_t dst = MERGE_BYTE4(b1, b2, b3, b4);           \
        b1 = next_byte(vm);                                   \
        b2 = next_byte(vm);                                   \
        b3 = next_byte(vm);                                   \
        b4 = next_byte(vm);                                   \
        uint32_t src1 = vm->data[MERGE_BYTE4(b1, b2, b3, b4)];\
        b1 = next_byte(vm);                                   \
        b2 = next_byte(vm);                                   \
        b3 = next_byte(vm);                                   \
        b4 = next_byte(vm);                                   \
        uint32_t src2 = vm->data[MERGE_BYTE4(b1, b2, b3, b4)];\
        uint32_t val = src1 __op src2;                        \
        memory_copy4(&vm->data[dst], &val);                   \
        if (val == 0)                                         \
        {                                                     \
            vm->z_flag = true;                                \
        }                                                     \
        else                                                  \
        {                                                     \
            vm->z_flag = false;                               \
        }                                                     \
        vm->ip += 1;                                          \
        break;                                                \
    }

enum opcode
{
    /** Early opcodes. */
    OP_EXIT = 0,
    OP_INT_STORE,
    OP_INT_TOSTRING,

    /** Jump operations. */
    OP_JUMP_TO = 0x10,
    OP_JUMP_Z,
    OP_JUMP_NZ,

    /** Bitwise & Math operations. */
    OP_XOR = 0x20,
    OP_AND,
    OP_OR,
    OP_SHL,
    OP_SHR,
    OP_ADD,
    OP_SUB,
    OP_MUL,
    OP_DIV,
    OP_INC,
    OP_DEC,
    OP_MOD,
    OP_GE,
    OP_LE,
    OP_NE,

    /** String operations. */
    OP_STRING_STORE = 0x30,
    OP_STRING_CONCAT,
    OP_STRING_TO_INT,

    /** Comparison & Test operations. */
    OP_CMP_REG = 0x40,
    OP_CMP_INTEGER,
    OP_CMP_CODE_STRING,
    OP_CMP_DATA_STRING,
    OP_CMP_RO_STRING,
    OP_CMP_POINTER,
    OP_CMP_RO_POINTER,
    OP_IS_STRING,
    OP_IS_INTEGER,
    OP_IS_POINTER,

    /** Misc. */
    OP_NOP = 0x50,
    OP_MOVE_REG,
    OP_PRE_SYSCALL,
    OP_SYSCALL,

    /** PEEK & POKE operations. */
    OP_PEEK = 0x60,
    OP_POKE,
    OP_MEMCPY,

    /** Stack operations. */
    OP_STACK_PUSH = 0x70,
    OP_STACK_PUSH_PTR,
    OP_STACK_PUSH_STR,
    OP_STACK_POP,
    OP_STACK_RET,
    OP_STACK_CALL,

    /** Pointer operations. */
    OP_PTR_STORE = 0x80,
    OP_PTR_TO_INT,

    /** Memory Bitwise & Math operations. */
    OP_XOR_M = 0x90,
    OP_AND_M,
    OP_OR_M,
    OP_SHL_M,
    OP_SHR_M,
    OP_ADD_M,
    OP_SUB_M,
    OP_MUL_M,
    OP_DIV_M,
    OP_INC_M,
    OP_DEC_M,
    OP_MOD_M,
    OP_GE_M,
    OP_LE_M,
    OP_NE_M,

    /** */
};

typedef struct
{
    uint16_t magic;
    uint16_t stack_size;
    uint32_t data_size;
    uint32_t startup;
} bytecode_header_t;

static inline uint8_t next_byte(em_vm_t *vm);

static inline uint8_t *string_from_code(em_vm_t *vm);

static inline const uint8_t *string_from_ro_data(em_vm_t *vm);

static inline uint8_t *string_from_data(em_vm_t *vm);

static uint32_t string_to_integer(uint8_t *str);

static uint8_t *integer_to_string(uint32_t num);

static uint32_t string_length(const uint8_t *str);

static bool string_compare(const uint8_t *str1, const uint8_t *str2);

static inline uint8_t *string_concat(const uint8_t *str1, const uint8_t *str2);

static void memory_set_0(void *dst, uint32_t size);

static void memory_copy(void *dst, const void *src, uint32_t size);

static inline void memory_copy4(void *dst, const void *src);

em_vm_t *create_vm(uint8_t *bytecode, reg_t (*syscall)(uint16_t, uint8_t *))
{
    em_vm_t *vm = vm_malloc(sizeof(em_vm_t));
    if (vm == NULL)
    {
        return NULL;
    }
    memory_set_0(vm, sizeof(em_vm_t));
    vm->bytecode = bytecode;
    bytecode_header_t *h = (bytecode_header_t *) vm->bytecode;

    vm->stack = vm_malloc(h->stack_size);
    vm->data = vm_malloc(h->data_size);
    memory_copy(vm->data, vm->bytecode + 12, h->data_size);
    vm->code = vm->bytecode + 12;
    vm->ip = h->startup;
    vm->syscall = syscall;

    vm->running = true;
    vm->z_flag = false;

    return vm;
}

bool vm_restart(em_vm_t *vm)
{
    if (vm == NULL || vm->bytecode == NULL)
    {
        return false;
    }
    vm->running = true;
    vm->z_flag = false;
    bytecode_header_t *h = (bytecode_header_t *) vm->bytecode;
    memory_copy(vm->data, vm->bytecode + 12, h->data_size);
    vm->ip = h->startup;
    vm->sp = 0;
    return true;
}

void vm_exec(em_vm_t *vm)
{
    while (vm->running)
    {
        switch (vm->code[vm->ip])
        {
            case OP_EXIT:
            {
                vm->running = false;
                break;
            }

            case OP_INT_STORE:
            {
                uint32_t reg = next_byte(vm);
                uint32_t b1 = next_byte(vm);
                uint32_t b2 = next_byte(vm);
                uint32_t b3 = next_byte(vm);
                uint32_t b4 = next_byte(vm);

                if (vm->registers[reg].type == STRING && vm->registers[reg].content.string != NULL)
                {
                    vm_free(vm->registers[reg].content.string);
                }
                vm->registers[reg].type = INTEGER;
                vm->registers[reg].content.integer = MERGE_BYTE4(b1, b2, b3, b4);

                vm->ip += 1;
                break;
            }

            case OP_INT_TOSTRING:
            {
                uint32_t reg = next_byte(vm);
                uint32_t val = (int32_t) vm->registers[reg].content.integer;

                vm->registers[reg].type = STRING;
                vm->registers[reg].content.string = integer_to_string(val);

                vm->ip += 1;
                break;
            }

            case OP_JUMP_TO:
            {
                uint32_t b1 = next_byte(vm);
                uint32_t b2 = next_byte(vm);
                uint32_t b3 = next_byte(vm);
                uint32_t b4 = next_byte(vm);

                uint32_t offset = MERGE_BYTE4(b1, b2, b3, b4);
                vm->ip = offset;
                break;
            }

            case OP_JUMP_Z:
            {
                uint32_t b1 = next_byte(vm);
                uint32_t b2 = next_byte(vm);
                uint32_t b3 = next_byte(vm);
                uint32_t b4 = next_byte(vm);

                uint32_t offset = MERGE_BYTE4(b1, b2, b3, b4);

                if (vm->z_flag)
                {
                    vm->ip = offset;
                }
                else
                {
                    vm->ip += 1;
                }
                break;
            }

            case OP_JUMP_NZ:
            {
                uint32_t b1 = next_byte(vm);
                uint32_t b2 = next_byte(vm);
                uint32_t b3 = next_byte(vm);
                uint32_t b4 = next_byte(vm);

                uint32_t offset = MERGE_BYTE4(b1, b2, b3, b4);

                if (!vm->z_flag)
                {
                    vm->ip = offset;
                }
                else
                {
                    vm->ip += 1;
                }
                break;
            }

            case OP_XOR:
            MATH_OP(^)

            case OP_AND:
            MATH_OP(&)

            case OP_OR:
            MATH_OP(|)

            case OP_SHL:
            MATH_OP(<<)

            case OP_SHR:
            MATH_OP(>>)

            case OP_ADD:
            MATH_OP(+)

            case OP_SUB:
            MATH_OP(-)

            case OP_MUL:
            MATH_OP(*)

            case OP_DIV:
            MATH_OP(/)

            case OP_MOD:
            MATH_OP(%)

            case OP_INC:
            {
                uint32_t reg = next_byte(vm);

                uint32_t b1 = next_byte(vm);
                uint32_t b2 = next_byte(vm);

                uint32_t val = MERGE_BYTE2(b1, b2);

                vm->registers[reg].content.integer += val;
                if (vm->registers[reg].content.integer == 0)
                {
                    vm->z_flag = true;
                }
                else
                {
                    vm->z_flag = false;
                }

                vm->ip += 1;
                break;
            }

            case OP_DEC:
            {
                uint32_t reg = next_byte(vm);

                uint32_t b1 = next_byte(vm);
                uint32_t b2 = next_byte(vm);

                uint32_t val = MERGE_BYTE2(b1, b2);

                vm->registers[reg].content.integer -= val;
                if (vm->registers[reg].content.integer == 0)
                {
                    vm->z_flag = true;
                }
                else
                {
                    vm->z_flag = false;
                }

                vm->ip += 1;
                break;
            }

            case OP_GE:
            {
                uint32_t reg1 = next_byte(vm);
                uint32_t reg2 = next_byte(vm);

                if (vm->registers[reg1].content.integer >= vm->registers[reg2].content.integer)
                {
                    vm->z_flag = true;
                }
                else
                {
                    vm->z_flag = false;
                }

                vm->ip += 1;
                break;
            }

            case OP_LE:
            {
                uint32_t reg1 = next_byte(vm);
                uint32_t reg2 = next_byte(vm);

                if (vm->registers[reg1].content.integer <= vm->registers[reg2].content.integer)
                {
                    vm->z_flag = true;
                }
                else
                {
                    vm->z_flag = false;
                }

                vm->ip += 1;
                break;
            }

            case OP_NE:
            {
                uint32_t reg1 = next_byte(vm);
                uint32_t reg2 = next_byte(vm);

                if (vm->registers[reg1].content.integer != vm->registers[reg2].content.integer)
                {
                    vm->z_flag = true;
                }
                else
                {
                    vm->z_flag = false;
                }

                vm->ip += 1;
                break;
            }

            case OP_STRING_STORE:
            {
                uint32_t reg = next_byte(vm);

                if (vm->registers[reg].type == STRING && vm->registers[reg].content.string != NULL)
                {
                    vm_free(vm->registers[reg].content.string);
                }

                vm->registers[reg].type = STRING;
                vm->registers[reg].content.string = string_from_code(vm);

                vm->ip += 1;
                break;
            }

            case OP_STRING_CONCAT:
            {
                uint32_t reg = next_byte(vm);
                uint32_t src1 = next_byte(vm);
                uint32_t src2 = next_byte(vm);

                uint8_t *str1 = vm->registers[src1].content.string;
                uint8_t *str2 = vm->registers[src2].content.string;

                if (vm->registers[reg].type == STRING && vm->registers[reg].content.string != NULL)
                {
                    vm_free(vm->registers[reg].content.string);
                }

                vm->registers[reg].type = STRING;
                vm->registers[reg].content.string = string_concat(str1, str2);

                vm->ip += 1;
                break;
            }

            case OP_STRING_TO_INT:
            {
                uint32_t reg = next_byte(vm);

                uint32_t integer = string_to_integer(vm->registers[reg].content.string);
                vm_free(vm->registers[reg].content.string);

                vm->registers[reg].type = INTEGER;
                vm->registers[reg].content.integer = integer;

                vm->ip += 1;
                break;
            }

            case OP_CMP_REG:
            {
                uint32_t reg1 = next_byte(vm);
                uint32_t reg2 = next_byte(vm);

                vm->z_flag = false;
                if (vm->registers[reg1].type == vm->registers[reg2].type)
                {
                    if (vm->registers[reg1].type == STRING)
                    {
                        uint8_t *str1 = vm->registers[reg1].content.string;
                        uint8_t *str2 = vm->registers[reg2].content.string;
                        if (string_compare(str1, str2))
                        {
                            vm->z_flag = true;
                        }
                    }
                    else if (vm->registers[reg1].type == INTEGER)
                    {
                        if (vm->registers[reg1].content.integer == vm->registers[reg2].content.integer)
                        {
                            vm->z_flag = true;
                        }
                    }
                    else
                    {
                        if (vm->registers[reg1].content.pointer == vm->registers[reg2].content.pointer)
                        {
                            vm->z_flag = true;
                        }
                    }
                }

                vm->ip += 1;
                break;
            }

            case OP_CMP_INTEGER:
            {
                uint32_t reg = next_byte(vm);

                uint32_t b1 = next_byte(vm);
                uint32_t b2 = next_byte(vm);
                uint32_t b3 = next_byte(vm);
                uint32_t b4 = next_byte(vm);

                uint32_t val = MERGE_BYTE4(b1, b2, b3, b4);

                if (vm->registers[reg].content.integer == val)
                {
                    vm->z_flag = true;
                }
                else
                {
                    vm->z_flag = false;
                }

                vm->ip += 1;
                break;
            }

            case OP_CMP_CODE_STRING:
            {
                uint32_t reg = next_byte(vm);

                uint8_t *str1 = vm->registers[reg].content.string;
                uint8_t *str2 = string_from_code(vm);

                if (string_compare(str1, str2))
                {
                    vm->z_flag = true;
                }
                else
                {
                    vm->z_flag = false;
                }

                vm_free(str2);
                vm->ip += 1;
                break;
            }

            case OP_CMP_DATA_STRING:
            {
                uint32_t reg = next_byte(vm);

                uint8_t *str1 = vm->registers[reg].content.string;
                const uint8_t *str2 = string_from_data(vm);

                if (string_compare(str1, str2))
                {
                    vm->z_flag = true;
                }
                else
                {
                    vm->z_flag = false;
                }

                vm->ip += 1;
                break;
            }

            case OP_CMP_RO_STRING:
            {
                uint32_t reg = next_byte(vm);

                uint8_t *str1 = vm->registers[reg].content.string;
                const uint8_t *str2 = string_from_ro_data(vm);

                if (string_compare(str1, str2))
                {
                    vm->z_flag = true;
                }
                else
                {
                    vm->z_flag = false;
                }

                vm->ip += 1;
                break;
            }

            case OP_CMP_POINTER:
            {
                uint32_t reg = next_byte(vm);

                uint32_t b1 = next_byte(vm);
                uint32_t b2 = next_byte(vm);
                uint32_t b3 = next_byte(vm);
                uint32_t b4 = next_byte(vm);

                uint32_t offset = MERGE_BYTE4(b1, b2, b3, b4);
                if (vm->registers[reg].content.pointer == &vm->data[offset])
                {
                    vm->z_flag = true;
                }
                else
                {
                    vm->z_flag = false;
                }

                vm->ip += 1;
                break;
            }

            case OP_CMP_RO_POINTER:
            {
                uint32_t reg = next_byte(vm);

                uint32_t b1 = next_byte(vm);
                uint32_t b2 = next_byte(vm);
                uint32_t b3 = next_byte(vm);
                uint32_t b4 = next_byte(vm);

                uint32_t offset = MERGE_BYTE4(b1, b2, b3, b4);
                if (vm->registers[reg].content.pointer == &vm->code[offset])
                {
                    vm->z_flag = true;
                }
                else
                {
                    vm->z_flag = false;
                }

                vm->ip += 1;
                break;
            }

            case OP_IS_STRING:
            {
                uint32_t reg = next_byte(vm);
                if (vm->registers[reg].type == STRING)
                {
                    vm->z_flag = true;
                }
                else
                {
                    vm->z_flag = false;
                }

                vm->ip += 1;
                break;
            }

            case OP_IS_INTEGER:
            {
                uint32_t reg = next_byte(vm);
                if (vm->registers[reg].type == INTEGER)
                {
                    vm->z_flag = true;
                }
                else
                {
                    vm->z_flag = false;
                }

                vm->ip += 1;
                break;
            }

            case OP_NOP:
            {
                vm->ip += 1;
                break;
            }

            case OP_MOVE_REG:
            {
                uint32_t dst = next_byte(vm);
                uint32_t src = next_byte(vm);

                if (vm->registers[dst].type == STRING && vm->registers[dst].content.string != NULL)
                {
                    vm_free(vm->registers[dst].content.string);
                }

                if (vm->registers[src].type == STRING)
                {
                    vm->registers[dst].type = STRING;
                    uint32_t len = string_length(vm->registers[src].content.string) + 1;
                    vm->registers[dst].content.string = vm_malloc(len);
                    memory_copy(vm->registers[dst].content.string, vm->registers[src].content.string, len);
                }
                else if (vm->registers[src].type == INTEGER)
                {
                    vm->registers[dst].type = INTEGER;
                    vm->registers[dst].content.integer = vm->registers[src].content.integer;
                }
                else
                {
                    vm->registers[dst].type = POINTER;
                    vm->registers[dst].content.pointer = vm->registers[src].content.pointer;
                }

                vm->ip += 1;
                break;
            }

            case OP_PRE_SYSCALL:
            {
                uint32_t reg = next_byte(vm);

                if (vm->registers[reg].type == STRING)
                {
                    vm_free(vm->registers[reg].content.string);
                }

                vm->registers[reg].type = INTEGER;
                vm->registers[reg].content.integer = vm->sp + 4;

                vm->ip += 1;
                break;
            }

            case OP_SYSCALL:
            {
                uint32_t reg = next_byte(vm);
                uint32_t b1 = next_byte(vm);
                uint32_t b2 = next_byte(vm);

                uint32_t id = MERGE_BYTE2(b1, b2);

                uint32_t stack_bottom = vm->registers[reg].content.integer;
                vm->registers[reg] = vm->syscall(id, &vm->stack[stack_bottom]);
                vm->sp = stack_bottom - 4;

                vm->ip += 1;
                break;
            }

            case OP_PEEK:
            {
                uint32_t reg = next_byte(vm);
                uint32_t src = next_byte(vm);

                uint32_t adr = vm->registers[src].content.integer;

                if (vm->registers[src].type == STRING && vm->registers[src].content.string != NULL)
                {
                    vm_free(vm->registers[src].content.string);
                }
                vm->registers[reg].type = INTEGER;

                memory_copy4(&vm->registers[reg].content.integer, &vm->data[adr]);

                vm->ip += 1;
                break;
            }

            case OP_POKE:
            {
                uint32_t reg = next_byte(vm);
                uint32_t src = next_byte(vm);

                uint32_t adr = vm->registers[src].content.integer;
                memory_copy4(&vm->data[adr], &vm->registers[reg].content.integer);

                vm->ip += 1;
                break;
            }

            case OP_MEMCPY:
            {
                uint32_t dst = vm->registers[next_byte(vm)].content.integer;
                uint32_t src = vm->registers[next_byte(vm)].content.integer;
                uint32_t size = vm->registers[next_byte(vm)].content.integer;

                memory_copy(&vm->data[dst], &vm->data[src], size);

                vm->ip += 1;
                break;
            }

            case OP_STACK_PUSH:
            {
                uint32_t reg = next_byte(vm);

                vm->sp += 4;
                memory_copy4(&vm->stack[vm->sp], &vm->registers[reg].content.integer);

                vm->ip += 1;
                break;
            }

            case OP_STACK_PUSH_PTR:
            {
                uint32_t reg = next_byte(vm);
                uint32_t len = sizeof(uint8_t *);
                vm->sp += 4;
                memory_copy(&vm->stack[vm->sp], &vm->registers[reg].content.pointer, len);

                vm->sp += ((int32_t) len - 4);  // 32 / 64 bit
                vm->ip += 1;
                break;
            }

            case OP_STACK_PUSH_STR:
            {
                uint32_t reg = next_byte(vm);
                uint32_t len = string_length(vm->registers[reg].content.string);
                vm->sp += 4;

                memory_copy(&vm->stack[vm->sp], vm->registers[reg].content.string, len + 1);
                if (len > 4)
                {
                    vm->sp += (len - 4);
                }

                vm->ip += 1;
                break;
            }

            case OP_STACK_POP:
            {
                uint32_t reg = next_byte(vm);
                uint32_t val;
                memory_copy4(&val, &vm->stack[vm->sp]);

                vm->sp -= 4;

                if (vm->registers[reg].type == STRING)
                {
                    vm_free(vm->registers[reg].content.string);
                }

                vm->registers[reg].type = INTEGER;
                vm->registers[reg].content.integer = val;

                vm->ip += 1;
                break;
            }

            case OP_STACK_RET:
            {
                uint32_t val;
                memory_copy4(&val, &vm->stack[vm->sp]);
                vm->sp -= 4;

                vm->ip = val;
                break;
            }

            case OP_STACK_CALL:
            {
                uint32_t b1 = next_byte(vm);
                uint32_t b2 = next_byte(vm);
                uint32_t b3 = next_byte(vm);
                uint32_t b4 = next_byte(vm);

                uint32_t offset = MERGE_BYTE4(b1, b2, b3, b4);
                uint32_t ip = vm->ip + 1;

                vm->sp += 4;
                memory_copy4(&vm->stack[vm->sp], &ip);

                vm->ip = offset;
                break;
            }

            case OP_PTR_STORE:
            {
                uint32_t reg = next_byte(vm);
                uint32_t b1 = next_byte(vm);
                uint32_t b2 = next_byte(vm);
                uint32_t b3 = next_byte(vm);
                uint32_t b4 = next_byte(vm);

                uint32_t offset = MERGE_BYTE4(b1, b2, b3, b4);
                if (vm->registers[reg].type == STRING)
                {
                    vm_free(vm->registers[reg].content.string);
                }
                vm->registers[reg].type = POINTER;
                vm->registers[reg].content.pointer = &vm->code[offset];

                vm->ip += 1;
                break;
            }

            case OP_PTR_TO_INT:
            {
                uint32_t reg = next_byte(vm);
                uint32_t reg1 = next_byte(vm);
                uint32_t reg2 = next_byte(vm);

                if (vm->registers[reg].type == STRING)
                {
                    vm_free(vm->registers[reg].content.string);
                }

                memory_copy4(&vm->registers[reg].content.integer,
                             vm->registers[reg1].content.pointer + vm->registers[reg2].content.integer);

                vm->ip += 1;
                break;
            }

            case OP_IS_POINTER:
            {
                uint32_t reg = next_byte(vm);
                if (vm->registers[reg].type == POINTER)
                {
                    vm->z_flag = true;
                }
                else
                {
                    vm->z_flag = false;
                }

                vm->ip += 1;
                break;
            }

            case OP_XOR_M:
            MATH_M_OP(^)

            case OP_AND_M:
            MATH_M_OP(&)

            case OP_OR_M:
            MATH_M_OP(|)

            case OP_SHL_M:
            MATH_M_OP(<<)

            case OP_SHR_M:
            MATH_M_OP(>>)

            case OP_ADD_M:
            MATH_M_OP(+)

            case OP_SUB_M:
            MATH_M_OP(-)

            case OP_MUL_M:
            MATH_M_OP(*)

            case OP_DIV_M:
            MATH_M_OP(/)

            case OP_MOD_M:
            MATH_M_OP(%)

            case OP_INC_M:
            {
                uint32_t b1, b2, b3, b4;
                b1 = next_byte(vm);
                b2 = next_byte(vm);
                b3 = next_byte(vm);
                b4 = next_byte(vm);

                uint32_t dst = MERGE_BYTE4(b1, b2, b3, b4);

                b1 = next_byte(vm);
                b2 = next_byte(vm);
                uint32_t val = MERGE_BYTE2(b1, b2);
                vm->data[dst] += val;

                vm->ip += 1;
                break;
            }

            case OP_DEC_M:
            {
                uint32_t b1, b2, b3, b4;
                b1 = next_byte(vm);
                b2 = next_byte(vm);
                b3 = next_byte(vm);
                b4 = next_byte(vm);

                uint32_t dst = MERGE_BYTE4(b1, b2, b3, b4);

                b1 = next_byte(vm);
                b2 = next_byte(vm);
                uint32_t val = MERGE_BYTE2(b1, b2);
                vm->data[dst] -= val;

                vm->ip += 1;
                break;
            }

            case OP_GE_M:
            {
                uint32_t b1, b2, b3, b4;
                b1 = next_byte(vm);
                b2 = next_byte(vm);
                b3 = next_byte(vm);
                b4 = next_byte(vm);
                uint32_t src1 = vm->data[MERGE_BYTE4(b1, b2, b3, b4)];

                b1 = next_byte(vm);
                b2 = next_byte(vm);
                b3 = next_byte(vm);
                b4 = next_byte(vm);
                uint32_t src2 = vm->data[MERGE_BYTE4(b1, b2, b3, b4)];

                if (src1 > src2)
                {
                    vm->z_flag = true;
                }
                else
                {
                    vm->z_flag = false;
                }

                vm->ip += 1;
                break;
            }

            case OP_LE_M:
            {
                uint32_t b1, b2, b3, b4;
                b1 = next_byte(vm);
                b2 = next_byte(vm);
                b3 = next_byte(vm);
                b4 = next_byte(vm);
                uint32_t src1 = vm->data[MERGE_BYTE4(b1, b2, b3, b4)];

                b1 = next_byte(vm);
                b2 = next_byte(vm);
                b3 = next_byte(vm);
                b4 = next_byte(vm);
                uint32_t src2 = vm->data[MERGE_BYTE4(b1, b2, b3, b4)];

                if (src1 < src2)
                {
                    vm->z_flag = true;
                }
                else
                {
                    vm->z_flag = false;
                }

                vm->ip += 1;
                break;
            }

            case OP_NE_M:
            {
                uint32_t b1, b2, b3, b4;
                b1 = next_byte(vm);
                b2 = next_byte(vm);
                b3 = next_byte(vm);
                b4 = next_byte(vm);
                uint32_t src1 = vm->data[MERGE_BYTE4(b1, b2, b3, b4)];

                b1 = next_byte(vm);
                b2 = next_byte(vm);
                b3 = next_byte(vm);
                b4 = next_byte(vm);
                uint32_t src2 = vm->data[MERGE_BYTE4(b1, b2, b3, b4)];

                if (src1 != src2)
                {
                    vm->z_flag = true;
                }
                else
                {
                    vm->z_flag = false;
                }

                vm->ip += 1;
                break;
            }

            default:
                break;
        }
    }
}

void vm_destroy(em_vm_t *vm)
{
    if (vm != NULL)
    {
        if (vm->stack != NULL)
        {
            vm_free(vm->stack);
        }

        if (vm->data != NULL)
        {
            vm_free(vm->data);
        }

        for (int i = 0; i < REGISTER_COUNT; i++)
        {
            if (vm->registers[i].type == STRING && vm->registers[i].content.string != NULL)
            {
                vm_free(vm->registers[i].content.string);
            }
        }
        vm_free(vm);
        vm = NULL;
    }
}

//////////////////////////////////////////////////

static inline uint8_t next_byte(em_vm_t *vm)
{
    vm->ip += 1;
    return (vm->code[vm->ip]);
}

static inline uint8_t *string_from_code(em_vm_t *vm)
{
    uint32_t len1 = next_byte(vm);
    uint32_t len2 = next_byte(vm);

    uint32_t len = MERGE_BYTE2(len1, len2);

    vm->ip += 1;
    uint8_t *tmp = vm_malloc(len + 1);
    memory_set_0(tmp, len + 1);

    for (int i = 0; i < (int) len; i++)
    {
        tmp[i] = vm->code[vm->ip];
        vm->ip++;
    }
    vm->ip -= 1;

    return tmp;
}

static inline const uint8_t *string_from_ro_data(em_vm_t *vm)
{
    uint32_t b1 = next_byte(vm);
    uint32_t b2 = next_byte(vm);
    uint32_t b3 = next_byte(vm);
    uint32_t b4 = next_byte(vm);

    uint32_t offset = MERGE_BYTE4(b1, b2, b3, b4);
    return &vm->code[offset];
}

static inline uint8_t *string_from_data(em_vm_t *vm)
{
    uint32_t b1 = next_byte(vm);
    uint32_t b2 = next_byte(vm);
    uint32_t b3 = next_byte(vm);
    uint32_t b4 = next_byte(vm);

    uint32_t offset = MERGE_BYTE4(b1, b2, b3, b4);
    return &vm->data[offset];
}

static uint32_t string_to_integer(uint8_t *str)
{
    uint32_t integer = 0;
    uint32_t len = string_length(str);
    if (str[0] == '-')
    {
        integer += (str[1] - 48);
        for (int i = 2; i < len; i++)
        {
            integer *= 10;
            integer += (str[i] - 48);
        }
        integer = -integer;
    }
    else
    {
        integer += (str[0] - 48);
        for (int i = 1; i < len; i++)
        {
            integer *= 10;
            integer += (str[i] - 48);
        }
    }
    return integer;
}

static uint8_t *integer_to_string(uint32_t num)
{
    static const char *s_base = "0123456789";
    uint8_t *str = vm_malloc(11);
    memory_set_0(str, 11);

    int len = 0;
    int start = 0;
    uint32_t tmp1 = num;
    uint32_t tmp2 = num;
    if ((int32_t) num < 0)
    {
        str[0] = '-';
        tmp1 = -num;
        tmp2 = -num;
        len++;
        start++;
    }
    do
    {
        len++;
        tmp1 /= 10;
    } while (tmp1 != 0);

    for (int i = len - 1; i >= start; i--)
    {
        str[i] = s_base[tmp2 % 10];
        tmp2 /= 10;
    }

    return str;
}

static uint32_t string_length(const uint8_t *str)
{
    uint32_t len = 0;
    do
    {
        len++;
        str++;
    } while (*str != '\0');

    return len;
}

static bool string_compare(const uint8_t *str1, const uint8_t *str2)
{
    int32_t str1_len = (int32_t) string_length(str1);
    int32_t i = 0;
    const uint8_t *p1, *p2;
    uint64_t *p64_1, *p64_2;
    uint32_t *p32_1, *p32_2;
    if (str1_len != string_length(str2))
    {
        return false;
    }

    for (; (str1_len - i) > 8; i += 8)
    {
        p1 = str1 + i;
        p2 = str2 + i;
        p64_1 = (uint64_t *) p1;
        p64_2 = (uint64_t *) p2;
        if (*p64_1 != *p64_2)
        {
            return false;
        }
    }
    for (; (str1_len - i) > 4; i += 4)
    {
        p1 = str1 + i;
        p2 = str2 + i;
        p32_1 = (uint32_t *) p1;
        p32_2 = (uint32_t *) p2;
        if (*p32_1 != *p32_2)
        {
            return false;
        }
    }
    for (; i < str1_len; i++)
    {
        p1 = str1 + i;
        p2 = str2 + i;
        if (*p1 != *p2)
        {
            return false;
        }
    }

    return true;
}

static inline uint8_t *string_concat(const uint8_t *str1, const uint8_t *str2)
{
    uint32_t str1_len = string_length(str1);
    uint32_t str2_len = string_length(str2);
    uint32_t str_len = str1_len + str2_len;
    uint8_t *str = vm_malloc(str_len + 1);

    memory_copy(str, str1, str1_len);
    memory_copy(str + str1_len, str2, str2_len);

    str[str_len] = '\0';
    return str;
}

static void memory_set_0(void *dst, uint32_t size)
{
    int32_t len = (int32_t) size;
    int32_t i = 0;
    uint8_t *p;
    uint64_t *p64;
    uint32_t *p32;
    for (; (len - i) > 8; i += 8)
    {
        p = dst + i;
        p64 = (uint64_t *) p;
        *p64 = 0;
    }
    for (; (len - i) > 4; i += 4)
    {
        p = dst + i;
        p32 = (uint32_t *) p;
        *p32 = 0;
    }
    for (; i < len; i++)
    {
        p = dst + i;
        *p = 0;
    }
}

static void memory_copy(void *dst, const void *src, uint32_t size)
{
    int32_t len = (int32_t) size;
    int32_t i = 0;
    uint8_t *p_dst;
    const uint8_t *p_src;
    uint64_t *p64_dst, *p64_src;
    uint32_t *p32_dst, *p32_src;

    for (; (len - i) > 8; i += 8)
    {
        p_dst = ((uint8_t *) dst) + i;
        p_src = ((uint8_t *) src) + i;
        p64_dst = (uint64_t *) p_dst;
        p64_src = (uint64_t *) p_src;
        *p64_dst = *p64_src;
    }
    for (; (len - i) > 4; i += 4)
    {
        p_dst = ((uint8_t *) dst) + i;
        p_src = ((uint8_t *) src) + i;
        p32_dst = (uint32_t *) p_dst;
        p32_src = (uint32_t *) p_src;
        *p32_dst = *p32_src;
    }
    for (; i < len; i++)
    {
        p_dst = ((uint8_t *) dst) + i;
        p_src = ((uint8_t *) src) + i;
        *p_dst = *p_src;
    }
}

static inline void memory_copy4(void *dst, const void *src)
{
    uint32_t *p32_dst = (uint32_t *) dst;
    const uint32_t *p32_src = (uint32_t *) src;
    *p32_dst = *p32_src;
}


